playback: smooth manual track switches and play/pause - #1740
Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves user-initiated track changes during gapless playback by adding a short fade-out/fade-in envelope around manual track switches, reducing audible clicks/pops caused by PCM discontinuities while keeping natural end-of-track gapless transitions unchanged.
Changes:
- Add a 20ms fade-out to silence based on the last submitted stereo frame when manually switching tracks in gapless mode.
- Apply a 20ms fade-in from silence on the decoded PCM of the newly selected track, including across packet boundaries.
- Add unit tests for fade endpoints and stereo/frame alignment, and document the fix in the changelog.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| playback/src/player.rs | Implements manual-switch fade-out/fade-in logic gated on gapless + track change, tracks last output frame, and adds unit tests. |
| CHANGELOG.md | Adds a “Fixed” entry documenting the manual gapless track-switch click prevention. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Would this apply to all play/pauses in the middle of songs? I get quite a decent pop from the speakers whenever I hit pause in the middle of a song. |
Reuse the 20 ms PCM envelope for mid-track pause and resume so the sink is not closed or reopened on a non-zero sample.
|
Thanks — it didn't originally, but it does now. Pause and resume use the same 20 ms fade as manual track switches: last frame down to silence before the sink stops, then the first decoded audio back in on play. |
Summary
Smooth abrupt PCM steps so they do not reach the audio backend:
This is a follow-up to #1213 and #1223. Keeping the sink open avoids a device reopen on track change, but a manually selected, un-preloaded track can still begin at a very different sample value. Pause still stops the sink, and resume starts it again; both used to do that on a non-zero sample. Those discontinuities are audible as a click or pop on some outputs. The short ramp implements the mute/unmute approach suggested in the issue discussion.
The track-switch ramp is limited to loads of a different track while the player is already
PlayingorPaused, and only when gapless mode is enabled. Pause and resume use the same envelope regardless of gapless, because they still close and reopen the sink. This does not change the public API, normal seeks, natural transitions, or passthrough packets.Testing
cargo fmt --all -- --checkcargo buildcargo clippycargo test --workspacecargo test -p librespot-playback --no-default-features --features pulseaudio-backend,native-tlsUnit tests cover exact fade endpoints, stereo channel alignment, fade-in state spanning decoder packet boundaries, and a no-op fade-in once the envelope is finished.
I also backported the same player change to librespot 0.8.0 in spotifyd 0.4.2 and tested repeated manual selections through the PulseAudio backend on PipeWire. The previously reproducible pop was no longer audible.